/* eslint-disable */
;(function (global) {
    'use strict';
    // Defines
    var moduleName = 'UmVideo',
        methods = {},
        root = global,
        previous_mymodule = global[moduleName],
        status = {'idle': 0, 'join': 1, 'ready': 2},
        iceservers =
        {
            'iceServers':
            [
                {
                    'urls':['stun:stun.stunprotocol.org:3478']
                },
                {
                    'urls': 'turn:numb.viagenie.ca:3478',
                    'credential': 'muazkh',
                    'username': 'webrtc@live.com'
                }
            ]
        },
        // {
        //     'iceServers':
        //     [
        //         {
        //             'urls':
        //             [
        //                 'turn:dapp.umnet.cn:3478?transport=udp',
        //                 'turn:dapp.umnet.cn:3478?transport=tcp',
        //                 'turn:dapp.umnet.cn:5349?transport=udp',
        //                 'turn:dapp.umnet.cn:5349?transport=tcp'
        //             ],
        //             'username':'1554543468:ninefingers',
        //             'credential':'J67AC6Wbh9M6x3VR615mTcqMVu0='
        //         },
        //         {
        //             'urls':
        //             [
        //                 'stun:dapp.umnet.cn:3478'
        //             ]
        //         }
        //     ]
        // },
        signalserver = 'wss://dapp.umnet.cn/signal',
        mediaconf = {audio:true, video: true},
        localvideo = 'video#local',
        remotes = '#remotes',
        role = 'people', // camera, observer
        videoelement = '<video id="###" playsinline autoplay></video>',
        getusermedianow = false, // get video imediatlly
        debuginfo = true,
        rateconf = {audio:500, video:5000} // rate configure. (kbps)
        ;

    // Private vars
    var m_conf = {iceservers, signalserver, mediaconf, localvideo, remotes, role, getusermedianow, videoelement, rateconf};
    var m_status = status.idle;
    var m_room_name = '';
    var m_room_info;
    var m_userid;
    var m_peer_conn = {};
    var m_ws = null;
    var m_stream;
    var m_event = {};


    // Private methods
    var _log = debuginfo?console.log:()=>{};
    var _err = console.error;

    // Set bitrate of audio and video, use 'm_conf.rateconf' value.
    var _setMediaBitrates = function (sdp) {
        if (m_conf.rateconf && m_conf.rateconf.video>0 && m_conf.rateconf.audio>0) {
            return _setMediaBitrate(_setMediaBitrate(sdp, "video", m_conf.rateconf.video), "audio", m_conf.rateconf.audio);
        }
        return sdp;
      }
      
    var _setMediaBitrate = function(sdp, media, bitrate) {
        var lines = sdp.split("\n");
        var line = -1;
        for (var i = 0; i < lines.length; i++) {
          if (lines[i].indexOf("m="+media) === 0) {
            line = i;
            break;
          }
        }
        if (line === -1) {
          console.debug("Could not find the m line for", media);
          return sdp;
        }
        console.debug("Found the m line for", media, "at line", line);
      
        // Pass the m line
        line++;
      
        // Skip i and c lines
        while(lines[line].indexOf("i=") === 0 || lines[line].indexOf("c=") === 0) {
          line++;
        }
      
        // If we're on a b line, replace it
        if (lines[line].indexOf("b") === 0) {
          console.debug("Replaced b line at line", line);
          lines[line] = "b=AS:"+bitrate;
          return lines.join("\n");
        }
        
        // Add a new b line
        console.debug("Adding new b line before line", line);
        var newLines = lines.slice(0, line)
        newLines.push("b=AS:"+bitrate)
        newLines = newLines.concat(lines.slice(line, lines.length))
        return newLines.join("\n")
      }

    var _initEvent = async function (onready, onerror) {

        let localView = document.querySelector(m_conf.localvideo);

        // set local view
        if (m_conf.getusermedianow && localView && !localView.srcObject) {
            let stream = await _get_user_media();
            localView.srcObject = stream;
        }

        if (!m_ws || m_ws.readyState !== m_ws.OPEN)
        {
            _log('Connect websocket to server:', m_conf.signalserver);
            if (m_ws) {
                _log('close pre websocket first.')
                m_ws.close();
            }

            // use websocket as signal channel
            m_ws = new WebSocket(m_conf.signalserver);
            m_ws.onopen = (evt) => {
                _log('Signal channel is ready.');
            };
            m_ws.onmessage = (evt) => {
                let data = JSON.parse(evt.data);
                _log('Recv signal: ', data);
                _onmessage(data);
                if (data.room && data.room.cmd==='userid') {
                    m_status = status.ready;
                    if (typeof(m_event['ready'])==='function') {
                        m_event['ready']();
                    }
                    if (onready) {
                        onready()
                    }
                }
            };
            m_ws.onclose = (evt) => {
                _log('Signal channel closed.');
                m_ws = null;
                if (onerror) {
                    onerror()
                }
            };
        }
        else
        {
            m_status = status.ready;
            if (typeof(m_event['ready'])==='function') {
                m_event['ready']();
            }
            if (onready) {
                onready()
            }
        }

        // ready
        _log('UmVideo Init OK');
    };

    var _pick_peer_conn = function (room) {
        let fromid = (room && room.fromid)?room.fromid:null;
        if (fromid && !m_peer_conn[fromid]) {
            let pc = new RTCPeerConnection(m_conf.iceservers);
            let isNegotiating = false;
            pc.onicecandidate = async ({candidate}) => {
                if (candidate) {
                    _send({candidate}, fromid);
                }
            }
            pc.onnegotiationneeded = async (candidate) => {
                if (isNegotiating) {
                    _log ('SKIP nested negotiations');
                    return;
                }
                isNegotiating = true;
                try {
                    let desc = await pc.createOffer();
                    await pc.setLocalDescription(desc); // Offer!!!
                    desc.sdp = _setMediaBitrates(desc.sdp);
                    _send({desc: desc}, fromid);
                } catch (err) {
                    _err(err);
                }
            };
            pc.ontrack = async (event) => {
                _log('ontrack from:', fromid);

                // camera no remote views
                if (m_conf.role === 'camera' ) return;

                let remoteElementId = 'a'+fromid;
                let remoteViews = document.querySelector(m_conf.remotes);
                let remoteView = document.querySelector('#'+remoteElementId);
                if (remoteView && remoteView.srcObject) return;
                remoteViews.appendChild(_createElementFromHTML(m_conf.videoelement.replace(/###/g, remoteElementId)));
                remoteView = document.querySelector('#'+remoteElementId);
                if (remoteView)
                    remoteView.srcObject = event.streams[0];
                else
                    _err('Can not create remote vidio element!');
            }
            pc.onsignalingstatechange = (e) => {  // Workaround for Chrome: skip nested negotiations
                isNegotiating = (pc.signalingState != 'stable');
            }
            m_peer_conn[fromid] = pc;
        }
        return m_peer_conn[fromid];
    };

    var _get_user_media = function () {
        var t = m_conf.mediaconf;
        let media = navigator.mediaDevices.getUserMedia(m_conf.mediaconf).catch(function(e) {
            if ("NotFoundError" !== e.name) {
                throw e;
            }
            return navigator.mediaDevices.enumerateDevices().then(function(e) {
                var n = e.find(function(e) {
                    return "videoinput" === e.kind
                })
                  , i = e.find(function(e) {
                    return "audioinput" === e.kind
                })
                  , r = {
                    video: n && t.video,
                    audio: i && t.audio
                };
                return navigator.mediaDevices.getUserMedia(r)
            })
        }).then(function(e) {
            return e;
        }).catch(function(e) {
            alert("无法打开本地视频或音频设备: " + e.message);
        })
        return media;
    };

    var _createElementFromHTML = function (htmlString) {
        var div = document.createElement('div');
        div.innerHTML = htmlString.trim();
        // Change this to div.childNodes to support multiple top-level nodes
        return div.firstChild; 
    }

    var _handle_offer = async function (desc, room) {
        _log('handle offer from:', room.fromid);
        let pc =  _pick_peer_conn(room);
        await pc.setRemoteDescription(desc);
        let localView = document.querySelector(m_conf.localvideo);
        let stream;
        if (localView && localView.srcObject) {
            stream = localView.srcObject;
        } else {
            stream = await _get_user_media();
            if (localView) {
                localView.srcObject = stream;
            }
        }
        stream.getTracks().forEach((track)=>pc.addTrack(track, stream));
        let answer = await pc.createAnswer();
        await pc.setLocalDescription(answer); // answer !!
        answer.sdp = _setMediaBitrates(answer.sdp);
        _send({desc: answer}, room.fromid);
    };

    var _handle_anwser = async function (desc, room) {
        _log('handle answer from:', room.fromid);
        let pc = _pick_peer_conn(room);
        await pc.setRemoteDescription(desc);
    };

    var _handle_candidate = async function (candidate, room) {
        _log('handle candidate');
        let pc = _pick_peer_conn(room);
        try {
            if (pc.signalingState !== 'have-local-offer') {
                _log(pc.signalingState);
                await pc.addIceCandidate(candidate);
            }
        } catch (err) {
            _err(err);
        }
    };

    var _handle_room_cmd = async function (room) {
        if (room) {
            if (room.cmd === 'hi') {
                let pc = _pick_peer_conn(room);
                let localView = document.querySelector(m_conf.localvideo);
                let stream;
                if (localView && localView.srcObject) {
                    stream = localView.srcObject;
                } else {
                    stream = await _get_user_media();
                    if (localView) {
                        localView.srcObject = stream;
                    }
                }
                stream.getTracks().forEach((track)=>pc.addTrack(track, stream));
            } 
            else if (room.cmd === 'hungup') {
                let key = room.fromid;
                _log("key值", key);
                _log("数组中的", m_peer_conn[key]);
                if (m_peer_conn[key]) {
                    m_peer_conn[key].close();
                    m_peer_conn[key] = undefined;
                    delete m_peer_conn[key];
                }
                let vl = document.querySelector('#a'+key);
                let vdl = document.querySelector('.a'+key);
                if (vl) vl.remove();
                if (vdl) vdl.remove();
            }
            else if (room.cmd === 'userid') {
                m_userid = room.yourid;
            }
            else if (typeof m_event[room.cmd] === 'undefined'){
                _err('Unsupport room command');
            }
            if (typeof(m_event[room.cmd])==='function') {
                m_event[room.cmd](room);
            }
        }
    }

    var _onmessage = function ({desc, candidate, room}) {
        //_log('recv:', desc, candidate, room);
        try {
            if (desc) {
                if (desc.type === 'offer') _handle_offer(desc, room);
                if (desc.type === 'answer') _handle_anwser(desc, room);
            }
            else if (candidate) {
                _handle_candidate(candidate, room);
            }
            else if (room) {
                _handle_room_cmd(room);
            }
            else {
                _err('Unsupport message type.');
            }
        } catch (err) {
            _err(err);
        }
    };

    var _send = function (msg, toid) {
        if (m_ws && m_ws.readyState === m_ws.OPEN) {
            if (!msg.room) {
                msg.room = {name: m_room_name, role: m_conf.role, info: m_room_info};
            } else {
                msg.room.name = m_room_name;
                if (!msg.room.role) msg.room.role = m_conf.role;
                msg.room.info = m_room_info;
            }
            if (toid) {
                msg.room.toid = toid;
            }
            _log ('send:', msg);
            m_ws.send(JSON.stringify(msg));
        } else {
            _log ('No websocket or websocket is not ready!');
        }
    }

    // Public methods

    /**
     * 得到UmCore的版本号
     */
    methods.version = function () {
        return '1.0.5.202006171446';
    };

    methods.server_ver = function () {
        _log('server_ver');
        _send({room:{cmd:'version'}});
    }

    /**
     * 列出所有房间, 回应事件roomlist
     */
    methods.listRooms = async function (role) {
        _log('list room', role);
        _send({room:{cmd:'list', role: role}});
    };

    /**
     * 列出房间内所有参会成员, 回应事件memberslist
     */
    methods.listMembers = async function (role) {
        _log('list member', role);
        _send({room:{cmd:'members', role: role}});
    };

    /**
     * 添加强制对方重连的指令， 如果传空值，则所有人强制重连
     */
    methods.forceReconnect = async function (userid) {
        _log('force reconnect:', userid);
        _send({room:{cmd:'forcereconnect'}}, userid);
    };

    /**
     * 加入视频房间
     * @param room_name  房间名
     * @param cid [可选] 对方设备id
     * @param info[可选] 自己的info
     */
    methods.joinRoom = async function (room_name, userid, info) {
        _log('join room:', room_name);
        m_status = status.join;
        m_room_name = room_name;
        m_room_info = info;
        _send({room:{cmd:'hi', name: m_room_name, role: m_conf.role, info: info}}, userid);
        // open local camera
        let localView = document.querySelector(m_conf.localvideo);
        if (localView && m_conf.getusermedianow && !localView.srcObject) {
            let stream = await _get_user_media();
            localView.srcObject = stream;
        }
    };

    /**
     * 退出视频房间
     * @param cid [可选] 对方设备id
     */
    methods.hungup = async function (userid) {
        _log('hungup');
        _send({room:{cmd:'hungup', name: m_room_name}}, userid);

        // check userid
        let pc = null;
        var has_userid = true;
        if (!userid ||
            (Object.keys(m_peer_conn).length==1 && m_peer_conn[userid])) {
            _log('no more userid or userid is null');
            has_userid = false;
        }

        // close a peer connection
        let closepc = (pc)=> {
            if (pc) {
                let senders = pc.getSenders();
                if (senders) {
                    senders.forEach( sender => {
                        if (sender.track)
                            sender.track.stop()
                    })
                }
                let recvers = pc.getReceivers();
                if (recvers) {
                    recvers.forEach( recver => {
                        if (recver.track)
                            recver.track.stop()
                    })
                }
                pc.close();
            }
        }

        // not has userid, close all
        if (!has_userid) {
            Object.keys(m_peer_conn).forEach((key)=>{
                _log('close peer:', key);
                pc = m_peer_conn[key];
                closepc(pc);
                m_peer_conn[key] = undefined;
                delete m_peer_conn[key];
            })
        } else {
            _log('close peer:', userid);
            pc = m_peer_conn[userid];
            closepc(pc);
            m_peer_conn[userid] = undefined;
            delete m_peer_conn[userid];
        }

        // remove all children
        let remoteViews = document.querySelector(m_conf.remotes);
        if (!has_userid) {
            while (remoteViews && remoteViews.firstChild) {
                remoteViews.removeChild(remoteViews.firstChild);
            }
        } else {
            var child = remoteViews.getElementsByTagName("video");
            for (let i = 0; i < child.length; i++) {
                if (child[i].id.substring(1) == userid) {
                    remoteViews.removeChild(child[i]);
                }
            }
        }

        // close local tracks
        if (!has_userid) {
            m_status = status.idle;
            let localView = document.querySelector(m_conf.localvideo);
            if (localView && localView.srcObject) {
                localView.srcObject.getTracks().forEach(track => track.stop());
                localView.srcObject = null;
            }
            // clear roomname
            m_room_name = "";
            m_room_info = undefined;

            // close websocket
            if (m_ws)
            {
                m_ws.close();
                m_ws = null;
            }
        }
    };

    /**
     * 设置UmCore环境，并重新建立信令通道
     */
    methods.setup = async function (conf) {
        m_conf = Object.assign(m_conf, conf);
        return new Promise((resolve, reject) => {
            _initEvent(resolve, reject);
        })
    };

    /**
     * 在特定事件触发。事件支持：
     * - event : ready, hi, userid, hungup
     */
    methods.on = function(event, func) {
        m_event[event] = func;
    };

    /**
     * 得到当前房间名称
     */
    methods.room = function() {
        return m_room_name;
    };

    /**
     * 得到当前房间信息
     */
    methods.room_info = function() {
        return m_room_info;
    };

    /**
     * 得到当前用户名称（信道名）
     */
    methods.userid = function() {
        return m_userid;
    };

    /**
     * 得到当前系统配置
     */
    methods.config = function() {
        return m_conf;
    };

    /**
     * 得到当前全部视频链接
     */
    methods.peers = function() {
        return m_peer_conn;
    }

    /**
     * 打开或关闭视频流
     */
    methods.setSelfVideo = function(enable) {
        let localView = document.querySelector(m_conf.localvideo);
        if (localView && localView.srcObject) {
            let lv = localView.srcObject.getVideoTracks();
            for(let i=0;i<lv.length;i++) {
                lv[i].enabled = enable;
            }
        } 

        for(let key in m_peer_conn) {
            let pc = m_peer_conn[key];
            if (!pc) continue;
            let senders = pc.getSenders();
            if (senders) {
                senders.forEach( sender => {
                    if (sender.track && sender.track.kind==='video') {
                        sender.track.enabled=enable
                    }
                })
            }
        }
    };

    /**
     * 打开或关闭音频流
     */
    methods.setSelfAudio = function(enable) {
        let localView = document.querySelector(m_conf.localvideo);
        if (localView && localView.srcObject) {
            let lv = localView.srcObject.getAudioTracks();
            for(let i=0;i<lv.length;i++) {
                lv[i].enabled = enable;
            }
        } 
        for(let key in m_peer_conn) {
            let pc = m_peer_conn[key];
            if (!pc) continue;
            let senders = pc.getSenders();
            if (senders) {
                senders.forEach( sender => {
                    if (sender.track && sender.track.kind==='audio') {
                        sender.track.enabled=enable
                    }
                })
            }
        } 
    };

    /**
     * 打开或关闭全局声音
     */
    methods.setOtherAudio = function (enable) {
        document.querySelectorAll("video").forEach(v => {
            if (v.srcObject) {
                v.srcObject.getAudioTracks().forEach(t=>t.enabled=enable)
            }
        })
    };

    setInterval(()=>{
        console.log("check websocket");
        if (!m_ws && m_status==status.join) {
            console.log("reconnecting...");
            //如果已经在房间内断线了，重连websocket
            m_ws = new WebSocket(m_conf.signalserver);
            m_ws.onopen = (evt) => {
                _log('Signal channel is ready.');
            };
            m_ws.onmessage = (evt) => {
                let data = JSON.parse(evt.data);
                _log('Recv signal: ', data);
                _onmessage(data);
            };
            m_ws.onclose = (evt) => {
                _log('Signal channel closed.');
                m_ws = null;
                if (onerror) {
                    onerror()
                }
            };
        }
    }, 3000);

    /**
     * 当前环境是否支持webrtc
     */
    methods.isWebRtcEnable = function() {
        return typeof(RTCPeerConnection)==="function";
    };

    // Support no conflict
    methods.noConflict = function() {
        root[moduleName] = previous_mymodule;
        return methods;
    };
    
    // - From async.js
    // AMD / RequireJS
    if (typeof define !== 'undefined' && define.amd) {
        define(moduleName, [], function () {
            return methods;
        });
    }
    // Node.js
    else if (typeof module !== 'undefined' && module.exports) {
        module.exports = methods;
    }
    // included directly via <script> tag
    else {
        global[moduleName] = methods;
    }

})(this);
